package com.baomidou.mybatisplus.core.conditions;

import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.enums.SqlLike;
import com.baomidou.mybatisplus.core.enums.WrapperKeyword;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.StringEscape;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
 * <p>查询条件封装</p>
 *
 * <p>项目名称：fakerMybatisPlusUtil</p>
 *
 * <p>注释:无</p>
 *
 * <p>Copyright: Copyright Faker(c) 2023/1/20</p>
 *
 * <p>公司: Faker</p>
 *
 * @author 淡梦如烟
 * @version 1.0
 * @date 2023/1/20 20:42
 */
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> {

    /**
     * 占位符
     */
    protected final Children typedThis = (Children) this;
    /**
     * 必要度量
     */
    protected AtomicInteger paramNameSeq;
    protected Map<String, Object> paramNameValuePairs;
    protected SharedString lastSql;
    /**
     * SQL注释
     */
    protected SharedString sqlComment;
    /**
     * SQL起始语句
     */
    protected SharedString sqlFirst;
    /**
     * ß
     * 数据库表映射实体类
     */
    private T entity;
    /**
     * 合并 SQL 片段
     */
    protected MergeSegments expression;
    /**
     * 实体类型(主要用于确定泛型以及取TableInfo缓存)
     */
    private Class<T> entityClass;

    @Override
    public T getEntity() {
        return entity;
    }

    public Children setEntity(T entity) {
        this.entity = entity;
        return typedThis;
    }

    protected Class<T> getEntityClass() {
        if (entityClass == null && entity != null) {
            entityClass = (Class<T>) entity.getClass();
        }
        return entityClass;
    }

    public Children setEntityClass(Class<T> entityClass) {
        if (entityClass != null) {
            this.entityClass = entityClass;
        }
        return typedThis;
    }

    /**
     * 对象是空的吗
     *
     * @param val
     * @return
     */
    public boolean isEmptyOrNull(Object val) {
        if (val == null) {
            return false;
        }
        if (val instanceof String) {
            if ("".equals((String) val)) {
                return false;
            } else {
                return true;
            }
        }
        if (val instanceof Object[]) {
            Object[] objects = (Object[]) val;
            if (objects.length <= 0) {
                return false;
            } else {
                return true;
            }
        }
        if (val instanceof List) {
            List list = (List) val;
            if (list.size() <= 0) {
                return false;
            } else {
                return true;
            }
        }
        if (val instanceof Collection) {
            Collection collection = (Collection) val;
            if (collection.size() <= 0) {
                return false;
            } else {
                return true;
            }
        }
        return true;
    }

    public Children eq(R column, Object val) {
        return eq(this.isEmptyOrNull(val), column, val);
    }

    public Children eq(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.EQ, val);
    }

    public Children ne(R column, Object val) {
        return ne(this.isEmptyOrNull(val), column, val);
    }

    public Children ne(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.NE, val);
    }

    public Children gt(R column, Object val) {
        return ge(this.isEmptyOrNull(val), column, val);
    }

    public Children gt(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.GT, val);
    }

    public Children ge(R column, Object val) {
        return ge(this.isEmptyOrNull(val), column, val);
    }

    public Children ge(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.GE, val);
    }

    public Children lt(R column, Object val) {
        return lt(this.isEmptyOrNull(val), column, val);
    }

    public Children lt(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.LT, val);
    }

    public Children le(R column, Object val) {
        return le(this.isEmptyOrNull(val), column, val);
    }

    public Children le(boolean condition, R column, Object val) {
        return addCondition(condition, column, SqlKeyword.LE, val);
    }

    public Children like(R column, Object val) {
        return like(this.isEmptyOrNull(val), column, val);
    }

    public Children like(boolean condition, R column, Object val) {
        return likeValue(condition, column, val, SqlLike.DEFAULT);
    }

    public Children notLike(R column, Object val) {
        return notLike(this.isEmptyOrNull(val), column, val);
    }

    public Children notLike(boolean condition, R column, Object val) {
        return not(condition).like(condition, column, val);
    }

    public Children likeLeft(R column, Object val) {
        return likeLeft(this.isEmptyOrNull(val), column, val);
    }

    public Children likeLeft(boolean condition, R column, Object val) {
        return likeValue(condition, column, val, SqlLike.LEFT);
    }

    public Children likeRight(R column, Object val) {
        return likeRight(this.isEmptyOrNull(val), column, val);
    }

    public Children likeRight(boolean condition, R column, Object val) {
        return likeValue(condition, column, val, SqlLike.RIGHT);
    }

    public Children between(R column, Object val1, Object val2) {
        return between(column, val1, val2);
    }

    public Children between(boolean condition, R column, Object val1, Object val2) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.BETWEEN, this.getCommonSqlSegment(formatSql("{0}", val1)), SqlKeyword.AND,
                this.getCommonSqlSegment(formatSql("{0}", val2)));
    }

    public Children notBetween(boolean condition, R column, Object val1, Object val2) {
        return not(condition).between(condition, column, val1, val2);
    }

    public Children and(Consumer<Children> consumer) {
        return and(true, consumer);
    }

    public Children and(boolean condition, Consumer<Children> consumer) {
        return and(condition).addNestedCondition(condition, consumer);
    }


    public Children or(boolean condition, Consumer<Children> consumer) {
        return or(condition).addNestedCondition(condition, consumer);
    }

    public Children nested(boolean condition, Consumer<Children> consumer) {
        return addNestedCondition(condition, consumer);
    }

    public Children or() {
        return or(true);
    }

    public Children or(boolean condition) {
        return doIt(condition, SqlKeyword.OR);
    }


    public Children apply(boolean condition, String applySql, Object... value) {
        return doIt(condition, WrapperKeyword.APPLY, this.getCommonSqlSegment(formatSql(applySql, value)));
    }

    public Children last(String lastSql) {
        return last(this.isEmptyOrNull(lastSql), lastSql);
    }

    public Children last(boolean condition, String lastSql) {
        if (condition) {
            this.lastSql.setStringValue(StringPool.SPACE + lastSql);
        }
        return typedThis;
    }

    public Children comment(boolean condition, String comment) {
        if (condition) {
            this.sqlComment.setStringValue(comment);
        }
        return typedThis;
    }

    public Children first(String firstSql) {
        return first(this.isEmptyOrNull(firstSql), firstSql);
    }

    public Children first(boolean condition, String firstSql) {
        if (condition) {
            this.sqlFirst.setStringValue(firstSql);
        }
        return typedThis;
    }

    public Children exists(String existsSql) {
        return exists(this.isEmptyOrNull(existsSql), existsSql);
    }

    public Children exists(boolean condition, String existsSql) {
        return doIt(condition, SqlKeyword.EXISTS, this.getCommonSqlSegment(String.format("(%s)", existsSql)));
    }


    public Children notExists(boolean condition, String notExistsSql) {
        return not(condition).exists(condition, notExistsSql);
    }

    public Children isNull(R column) {
        return isNull(this.isEmptyOrNull(column), column);
    }

    public Children isNull(boolean condition, R column) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.IS_NULL);
    }

    public Children isNotNull(R column) {
        return isNotNull(this.isEmptyOrNull(column), column);
    }

    public Children isNotNull(boolean condition, R column) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.IS_NOT_NULL);
    }

    public Children in(R column, Object[] coll) {
        return in(this.isEmptyOrNull(coll), column, Arrays.asList(coll));
    }

    public Children in(R column, Collection<?> coll) {
        return in(this.isEmptyOrNull(coll), column, coll);
    }

    public Children in(boolean condition, R column, Collection<?> coll) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.IN, inExpression(coll));
    }


    public Children notIn(boolean condition, R column, Collection<?> coll) {
        return not(condition).in(condition, column, coll);
    }


    public Children inSql(boolean condition, R column, String inValue) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.IN, this.getCommonSqlSegment(String.format("(%s)", inValue)));
    }


    public Children notInSql(boolean condition, R column, String inValue) {
        return not(condition).inSql(condition, column, inValue);
    }

    public Children groupBy(R... columns) {
        return groupBy(this.isEmptyOrNull(columns), columns);
    }

    public Children groupBy(boolean condition, R... columns) {
        if (ArrayUtils.isEmpty(columns)) {
            return typedThis;
        }
        return doIt(condition, SqlKeyword.GROUP_BY,
                this.getCommonSqlSegment(columns.length == 1 ? columnToString(columns[0]) : columnsToString(columns)));
    }

    public Children orderByAsc(R... columns) {
        return orderBy(this.isEmptyOrNull(columns), true, columns);
    }

    public Children orderByDesc(R... columns) {
        return orderBy(this.isEmptyOrNull(columns), false, columns);
    }

    public Children orderBy(boolean isAsc, R... columns) {
        return orderBy(this.isEmptyOrNull(columns), isAsc, columns);
    }

    public Children orderBy(boolean condition, boolean isAsc, R... columns) {
        if (ArrayUtils.isEmpty(columns)) {
            return typedThis;
        }
        SqlKeyword mode = isAsc ? SqlKeyword.ASC : SqlKeyword.DESC;
        for (R column : columns) {
            doIt(condition, SqlKeyword.ORDER_BY, this.getCommonSqlSegment(columnToString(column)), mode);
        }
        return typedThis;
    }


    public Children having(boolean condition, String sqlHaving, Object... params) {
        return doIt(condition, SqlKeyword.HAVING, this.getCommonSqlSegment(formatSqlIfNeed(condition, sqlHaving, params)));
    }


    public Children func(boolean condition, Consumer<Children> consumer) {
        if (condition) {
            consumer.accept(typedThis);
        }
        return typedThis;
    }

    /**
     * 内部自用
     * <p>NOT 关键词</p>
     */
    protected Children not(boolean condition) {
        return doIt(condition, SqlKeyword.NOT);
    }

    public Children and() {
        return and(true);
    }

    /**
     * 内部自用
     * <p>拼接 AND</p>
     */
    protected Children and(boolean condition) {
        return doIt(condition, SqlKeyword.AND);
    }

    /**
     * 内部自用
     * <p>拼接 LIKE 以及 值</p>
     */
    protected Children likeValue(boolean condition, R column, Object val, SqlLike sqlLike) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), SqlKeyword.LIKE, this.getCommonSqlSegment(formatSql("{0}", SqlUtils.concatLike(val, sqlLike))));
    }

    /**
     * 普通查询条件
     *
     * @param condition  是否执行
     * @param column     属性
     * @param sqlKeyword SQL 关键词
     * @param val        条件值
     */
    protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
        return doIt(condition, this.getCommonSqlSegment(columnToString(column)), sqlKeyword, this.getCommonSqlSegment(formatSql("{0}", val)));
    }

    /**
     * 多重嵌套查询条件
     *
     * @param condition 查询条件值
     */
    protected Children addNestedCondition(boolean condition, Consumer<Children> consumer) {
        if (condition) {
            final Children instance = instance();
            consumer.accept(instance);
            return doIt(true, WrapperKeyword.BRACKET, instance);
        }
        return typedThis;
    }

    /**
     * 子类返回一个自己的新对象
     */
    protected abstract Children instance();

    /**
     * 格式化SQL
     *
     * @param sqlStr SQL语句部分
     * @param params 参数集
     * @return sql
     */
    protected final String formatSql(String sqlStr, Object... params) {
        return formatSqlIfNeed(true, sqlStr, params);
    }

    /**
     * <p>
     * 根据需要格式化SQL<br>
     * <br>
     * Format SQL for methods: EntityQ<T>.where/and/or...("name={0}", value);
     * ALL the {<b>i</b>} will be replaced with #{MPGENVAL<b>i</b>}<br>
     * <br>
     * ew.where("sample_name=<b>{0}</b>", "haha").and("sample_age &gt;<b>{0}</b>
     * and sample_age&lt;<b>{1}</b>", 18, 30) <b>TO</b>
     * sample_name=<b>#{MPGENVAL1}</b> and sample_age&gt;#<b>{MPGENVAL2}</b> and
     * sample_age&lt;<b>#{MPGENVAL3}</b><br>
     * </p>
     *
     * @param need   是否需要格式化
     * @param sqlStr SQL语句部分
     * @param params 参数集
     * @return sql
     */
    protected final String formatSqlIfNeed(boolean need, String sqlStr, Object... params) {
        if (!need || StringUtils.isBlank(sqlStr)) {
            return null;
        }
        if (ArrayUtils.isNotEmpty(params)) {
            for (int i = 0; i < params.length; ++i) {
                String genParamName = Constants.WRAPPER_PARAM + paramNameSeq.incrementAndGet();
                sqlStr = sqlStr.replace(String.format("{%s}", i),
                        String.format(Constants.WRAPPER_PARAM_FORMAT, Constants.WRAPPER, genParamName));
                paramNameValuePairs.put(genParamName, params[i]);
            }
        }
        return sqlStr;
    }

    /**
     * 获取in表达式 包含括号
     *
     * @param value 集合
     */
    private ISqlSegment inExpression(final Collection<?> value) {
        return new ISqlSegment() {
            /**
             * SQL 片段
             */
            @Override
            public String getSqlSegment() {
                Iterator<?> iterator = value.iterator();
                StringBuffer sqlStr = new StringBuffer(StringPool.LEFT_BRACKET);
                while (iterator.hasNext()) {
                    sqlStr.append(formatSql("{0}", iterator.next())).append(StringPool.COMMA);
                }
                sqlStr.setLength(sqlStr.length() - StringPool.COMMA.length());
                sqlStr.append(StringPool.RIGHT_BRACKET);
                return sqlStr.toString();
            }
        };
    }

    /**
     * 必要的初始化
     */
    protected void initNeed() {
        paramNameSeq = new AtomicInteger(0);
        paramNameValuePairs = new HashMap(16);
        expression = new MergeSegments();
        lastSql = SharedString.emptyString();
        sqlComment = SharedString.emptyString();
        sqlFirst = SharedString.emptyString();
    }

    @Override
    public void clear() {
        entity = null;
        paramNameSeq.set(0);
        paramNameValuePairs.clear();
        expression.clear();
        lastSql.toEmpty();
        sqlComment.toEmpty();
        sqlFirst.toEmpty();
    }

    /**
     * 对sql片段进行组装
     *
     * @param condition   是否执行
     * @param sqlSegments sql片段数组
     * @return children
     */
    protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
        if (condition) {
            expression.add(sqlSegments);
        }
        return typedThis;
    }

    @Override
    public String getSqlSegment() {
        return expression.getSqlSegment() + lastSql.getStringValue();
    }

    @Override
    public String getSqlComment() {
        if (StringUtils.isNotBlank(sqlComment.getStringValue())) {
            return "/*" + StringEscape.escapeRawString(sqlComment.getStringValue()) + "*/";
        }
        return null;
    }

    @Override
    public String getSqlFirst() {
        if (StringUtils.isNotBlank(sqlFirst.getStringValue())) {
            return StringEscape.escapeRawString(sqlFirst.getStringValue());
        }
        return null;
    }


    public MergeSegments getExpression() {
        return expression;
    }

    public Map<String, Object> getParamNameValuePairs() {
        return paramNameValuePairs;
    }

    /**
     * 获取 columnName
     */
    protected String columnToString(R column) {
        return (String) column;
    }

    /**
     * 多字段转换为逗号 "," 分割字符串
     *
     * @param columns 多字段
     */
    protected String columnsToString(R... columns) {
//        return Arrays.stream(columns).map(this::columnToString).collect(joining(StringPool.COMMA));
        StringBuffer sqlStr = new StringBuffer();
        for (int i = 0; i < columns.length; i++) {
            if (null != columns[i]) {
                sqlStr.append(columns[i]).append(StringPool.COMMA);
            }
        }
        sqlStr.setLength(sqlStr.length() - 1);
        return sqlStr.toString();
    }

    @Override
    @SuppressWarnings("all")
    public Children clone() {
        return SerializationUtils.clone(typedThis);
    }


    /**
     * 自定义getCommonSqlSegment
     *
     * @param columnToString
     * @return
     */
    private ISqlSegment getCommonSqlSegment(final String columnToString) {
        return new ISqlSegment() {

            /**
             * SQL 片段
             */
            @Override
            public String getSqlSegment() {
                return columnToString;
            }
        };
    }

    public AtomicInteger getParamNameSeq() {
        return paramNameSeq;
    }
}
